package cn.kinoko.service.cnTeam.impl;

import cn.kinoko.model.cnTeam.constant.Position;
import cn.kinoko.common.base.model.BaseServiceImpl;
import cn.kinoko.common.code.ErrorCodeEnum;
import cn.kinoko.common.model.KV;
import cn.kinoko.common.utils.*;
import cn.kinoko.mapper.cnTeam.CnTeamMapper;
import cn.kinoko.model.cnTeam.entity.CnTeam;
import cn.kinoko.model.cnTeam.entity.CnTeamMember;
import cn.kinoko.model.cnTeam.query.CnTeamQuery;
import cn.kinoko.model.cnTeam.req.CnTeamPositionReq;
import cn.kinoko.model.cnTeam.req.CnTeamReq;
import cn.kinoko.model.cnTeam.vo.MemberVO;
import cn.kinoko.model.cnTeam.vo.CnTeamPageVO;
import cn.kinoko.model.cnTeam.vo.CnTeamVO;
import cn.kinoko.model.manga.entity.Manga;
import cn.kinoko.model.manga.vo.MangaCoverVO;
import cn.kinoko.service.cnTeam.PositionService;
import cn.kinoko.service.cnTeam.CnTeamMemberService;
import cn.kinoko.service.cnTeam.CnTeamService;
import cn.kinoko.service.common.util.AuthUtil;
import cn.kinoko.service.common.util.ThumbPathHelper;
import cn.kinoko.service.manga.MangaService;
import cn.kinoko.service.minio.MinioService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

/**
 * @author kinoko
 */
@Service
public class CnTeamServiceImpl extends BaseServiceImpl<CnTeamMapper, CnTeam> implements CnTeamService {

    @Resource
    private ExecutorService executorService;
    @Lazy
    @Resource
    private MangaService mangaService;
    @Resource
    private CnTeamMemberService cnTeamMemberService;
    @Resource
    private PositionService positionService;
    @Resource
    private MinioService minioService;

    /**
     * 分页查询
     *
     * @param query 查询条件
     * @return 分页结果
     */
    @SneakyThrows
    @Override
    public Page<CnTeamPageVO> queryPage(CnTeamQuery query) {
        Long uid = AuthUtil.getUidAndCheck();
        // 查询当前用户加入的汉化组
        List<Long> teamIdList = cnTeamMemberService.queryJoinTeamByUid(uid);
        // 如果查询的是当前用户的，则填充uid
        if (query.getIsMine() != null && query.getIsMine()) {
            query.setCurrentUid(uid);
        }
        Page<CnTeamPageVO> page = getBaseMapper().queryPage(getPage(query), query);
        // 查询当前分页汉化组的Top5热门作
        CompletableFutureUtil.runAsyncForEach(page.getRecords(), cnTeamPageVO -> {
            // （※ 这里采用循环查库，由于需求特殊，SQL语句上优化存在上限，批量查询存在大批次数据隐患，故折中使用虚拟线程加快查询速度）
            // 查询热门作品
            List<MangaCoverVO> top5MangaList = mangaService.queryTop5MangaByTid(cnTeamPageVO.getTid());
            cnTeamPageVO.setHotWorks(top5MangaList);
            // 设置logo缩略图
            ThumbPathHelper.toThumbPath(cnTeamPageVO, CnTeamPageVO::getLogoUrl, CnTeamPageVO::setThumbLogoUrl);
            // 判断是否加入汉化组
            if (teamIdList.contains(cnTeamPageVO.getTid())) {
                cnTeamPageVO.setJoin(true);
            }
            // 判断是否为组长
            if (cnTeamPageVO.getCreateBy().equals(uid)) {
                cnTeamPageVO.setLeader(true);
            }
        }, executorService);
        return page;
    }

    /**
     * 新增汉化组
     *
     * @param req 请求参数
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean save(CnTeamReq req) {
        // 获取当前登录用户
        Long uid = AuthUtil.getUidAndCheck();
        CnTeam cnTeam = BeanHelper.copyProperties(req, CnTeam.class);
        assert cnTeam != null;
        // 如果没有设置邀请码则生成
        if (req.getJoinCode() == null) {
            String joinCode = RandomStrGenerator.generate(10);
            // 查询数据库是否存在邀请码，存在则重新生成
            while (checkCodeExist(joinCode)) {
                joinCode = RandomStrGenerator.generate(10);
            }
            cnTeam.setJoinCode(joinCode);
        }
        // 保存
        save(cnTeam);
        // 加入汉化组
        CnTeamMember cnTeamMember = new CnTeamMember();
        cnTeamMember.setUid(uid);
        cnTeamMember.setTId(cnTeam.getTid());
        cnTeamMember.setPosition(BitmapUtil.setBitAt(0L, Position.LEADER.getCode().intValue()));
        cnTeamMemberService.save(cnTeamMember);
        // 添加封面的引用
        minioService.saveFileReference(Collections.singletonList(cnTeam.getLogoUrl()));
        return true;
    }

    /**
     * 更新汉化组
     *
     * @param req 请求参数
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean update(CnTeamReq req) {
        CnTeam cnTeam = getAndCheckCnTeam(req.getTid());
        // 判断是否修改了汉化组名称
        if (!req.getTeamName().equals(cnTeam.getTeamName())) {
            // 更新对应漫画中的冗余字段
            mangaService.lambdaUpdate()
                    .eq(Manga::getTid, cnTeam.getTid())
                    .set(Manga::getCnTeam, req.getTeamName())
                    .update();
        }
        // 判断封面是否替换
        if (StringUtils.isNotBlank(req.getLogoUrl()) && !req.getLogoUrl().equals(cnTeam.getLogoUrl())) {
            // 删除旧封面的引用
            minioService.deleteFileReference(Collections.singletonList(cnTeam.getLogoUrl()));
            // 添加新封面的引用
            minioService.saveFileReference(Collections.singletonList(req.getLogoUrl()));
        }
        // 拷贝属性
        BeanUtils.copyProperties(req, cnTeam);
        // 更新汉化组信息
        return updateById(cnTeam);
    }

    /**
     * 检查邀请码是否存在
     *
     * @param joinCode 邀请码
     * @return 是否存在
     */
    @Override
    public boolean checkCodeExist(String joinCode) {
        return lambdaQuery().select(CnTeam::getJoinCode).eq(CnTeam::getJoinCode, joinCode).exists();
    }

    /**
     * 获取汉化组详情
     *
     * @param tId 汉化组id
     * @return 汉化组详情
     */
    @Override
    public CnTeamVO detail(Long tId) {
        CnTeam cnTeam = getAndCheckCnTeam(tId);
        CnTeamVO cnTeamVO = BeanHelper.copyProperties(cnTeam, CnTeamVO.class);
        assert cnTeamVO != null;
        if (StringUtils.isNotBlank(cnTeamVO.getLogoUrl())) {
            // 填充缩略图
            ThumbPathHelper.toThumbPath(cnTeamVO, CnTeam::getLogoUrl, CnTeamVO::setThumbLogoUrl);
        }
        return cnTeamVO;
    }

    /**
     * 删除汉化组
     *
     * @param tId 汉化组id
     * @return 执行结果
     */
    @Override
    public boolean deleted(Long tId) {
        // 获取汉化组信息
        getAndCheckCnTeam(tId);
        return removeById(tId);
    }

    /**
     * 加入汉化组
     *
     * @param tid      汉化组id
     * @param joinCode 邀请码
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean joinTeam(Long tid, String joinCode) {
        Long uid = AuthUtil.getUidAndCheck();
        CnTeam cnTeam = getAndCheckCnTeam(tid);
        ExceptionUtil.assertion(!cnTeam.getJoinCode().equals(joinCode), "邀请码错误");
        // 查看是否已经加入
        ExceptionUtil.assertion(cnTeamMemberService.lambdaQuery()
                        .eq(CnTeamMember::getTId, tid)
                        .eq(CnTeamMember::getUid, uid)
                        .exists(), "你已经在该组了哦~");
        // 更新汉化组成员数量 + 1
        lambdaUpdate()
                .eq(CnTeam::getTid, tid)
                .set(CnTeam::getMemberCount, cnTeam.getMemberCount() + 1)
                .update();
        // 添加汉化组成员
        CnTeamMember cnTeamMember = new CnTeamMember();
        cnTeamMember.setTId(tid);
        cnTeamMember.setUid(uid);
        cnTeamMemberService.save(cnTeamMember);
        return true;
    }

    /**
     * 查询汉化组成员列表
     *
     * @param tid 汉化组id
     * @return 成员列表
     */
    @Override
    public List<MemberVO> teamMember(Long tid) {
        getAndCheckCnTeam(tid);
        // 查询汉化组成员
        return cnTeamMemberService.queryTeamMember(tid);
    }

    /**
     * 获取汉化组职位列表
     *
     * @return 职位列表
     */
    @Override
    public List<KV<Long, String>> positionList() {
        return positionService.queryAll(null).entrySet().stream().map(e -> new KV<>(e.getKey(), e.getValue())).toList();
    }

    /**
     * 更新汉化组职位
     *
     * @param req 请求参数
     * @return 执行结果
     */
    @Override
    public boolean changePosition(CnTeamPositionReq req) {
        // 查询汉化组
        CnTeam cnTeam = getAndCheckCnTeam(req.getTid());
        // 查看是否有该职位
        Set<Long> allPositionId = positionService.queryAll(null).keySet();
        boolean unknownPosition = req.getPositionIds().stream().anyMatch(id -> !allPositionId.contains(id));
        ExceptionUtil.assertion(unknownPosition, "存在未知职位");
        // 判断如果是汉化组创建人则无法移除组长职位
        if (cnTeam.getCreateBy().equals(req.getUid()) && !req.getPositionIds().contains(Position.LEADER.getCode())) {
            ExceptionUtil.throwOut("无法移除组长的职位~");
        }
        // 查询是否加入当前汉化组
        CnTeamMember cnTeamMember = cnTeamMemberService.lambdaQuery()
                .eq(CnTeamMember::getTId, req.getTid())
                .eq(CnTeamMember::getUid, req.getUid())
                .list().stream().findFirst().orElse(null);
        ExceptionUtil.assertion(cnTeamMember == null, "未找到该成员~");
        // 更新汉化组职位
        long position = BitmapUtil.setBitAt(0, req.getPositionIds());
        cnTeamMember.setPosition(position);
        return cnTeamMemberService.updateById(cnTeamMember);
    }

    /**
     * 移出汉化组成员
     *
     * @param tid 汉化组id
     * @param uid 用户id
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean removeMember(Long tid, Long uid) {
        CnTeam cnTeam = getAndCheckCnTeam(tid);
        // 判断用户是否为创建人
        if (cnTeam.getCreateBy().equals(uid)) {
            ExceptionUtil.throwOut("无法移除组长哦~");
        }
        // 移出汉化组成员
        return removeTeamMember(tid, uid, cnTeam);
    }

    /**
     * 退出汉化组
     *
     * @param tid 汉化组id
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean quitTeam(Long tid) {
        Long uid = AuthUtil.getUidAndCheck();
        CnTeam cnTeam = getAndCheckCnTeam(tid);
        // 判断是否为创建人
        if (cnTeam.getCreateBy().equals(uid)) {
            ExceptionUtil.throwOut("组长无法退出哦~");
        }
        // 判断是否加入汉化组
        boolean isJoin = cnTeamMemberService.lambdaQuery()
                .eq(CnTeamMember::getTId, tid)
                .eq(CnTeamMember::getUid, uid)
                .exists();
        ExceptionUtil.assertion(!isJoin, "你还未加入该组哦~");
        // 移出汉化组成员
        return removeTeamMember(tid, uid, cnTeam);
    }

    /**
     * 查询加入的汉化组（下拉框）
     *
     * @return 列表
     */
    @Override
    public List<KV<Long, String>> queryJoinTeamDdBox() {
        Long uid = AuthUtil.getUidAndCheck();
        return queryJoinTeamMap(uid).entrySet().stream().map(e -> new KV<>(e.getKey(), e.getValue())).toList();
    }

    /**
     * 查询用户加入的汉化组map
     *
     * @param uid 用户id
     * @return map
     */
    @Override
    public Map<Long, String> queryJoinTeamMap(Long uid) {
        return getBaseMapper().getJoinTeamMap(uid).stream()
                .collect(Collectors.toMap(e -> Long.parseLong(e.get("t_id").toString()), e -> e.get("team_name").toString()));
    }

    /**
     * 移出汉化组成员
     *
     * @param tid      汉化组id
     * @param uid      用户id
     * @param cnTeam   汉化组信息
     * @return 执行结果
     */
    private boolean removeTeamMember(Long tid, Long uid, CnTeam cnTeam) {
        // 移出汉化组成员
        cnTeamMemberService.lambdaUpdate()
                .eq(CnTeamMember::getTId, tid)
                .eq(CnTeamMember::getUid, uid)
                .remove();
        // 汉化组成员数量 - 1
        cnTeam.setMemberCount(cnTeam.getMemberCount() - 1);
        return updateById(cnTeam);
    }

    /**
     * 获取并检查汉化组信息
     *
     * @param tId 汉化组id
     * @return 汉化组信息
     */
    private CnTeam getAndCheckCnTeam(Long tId) {
        CnTeam cnTeam = getById(tId);
        ExceptionUtil.isNull(cnTeam, ErrorCodeEnum.SYSTEM_ERROR_B0406);
        return cnTeam;
    }
}